
// C std
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>

// linux
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/signal.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <setjmp.h>

#include "rtt.h"

#define MAXLINE 1500

typedef struct sockaddr    SA;
typedef struct sockaddr_in SAI;

static sigjmp_buf jmp_buff;

static struct rtt_info rtt_info;
static int    rttint = 0;
static struct msghdr message_send, message_recevie; /* assume init to zero */

static struct hdr {

    uint32_t    seq;
    uint32_t     ts;

}send_hdr, recevie_hdr;

static void sig_alarm(int signal_no);

// 主要的处理函数
ssize_t Dg_send_recv(int fd, const void * out_buffer, size_t out_bytes, 
    void * in_buffer, size_t in_bytes, const SA * destination_address, socklen_t destination_len);

void sig_alarm(int signo) {

    siglongjmp(jmp_buff, 1);
}

// 客户端函数
void dg_cli(FILE * fp, int sockfd, const SA * ptr_server_address, socklen_t serv_len) {

    ssize_t n;
    char  send_line[MAXLINE], recevie_line[MAXLINE];

    bzero(send_line, MAXLINE);
    bzero(recevie_line, MAXLINE);
    while (fgets(send_line, MAXLINE, fp) != NULL) {

        n = Dg_send_recv(sockfd, send_line, strlen(send_line), recevie_line, MAXLINE, ptr_server_address, serv_len);

        recevie_line[n] = '\0';
        fputs(recevie_line, stdout);
    }
}

ssize_t Dg_send_recv(int fd, const void * out_buffer, size_t out_bytes, 
    void * in_buffer, size_t in_bytes, const SA * destination_address, socklen_t destination_len) {

    ssize_t n = 0;
    struct iovec iov_send[2], iov_recevie[2];

    if (rttint == 0) {

        rtt_init(&rtt_info);
        bzero(&send_hdr, sizeof(send_hdr));
        bzero(&recevie_hdr, sizeof(recevie_hdr));
        rttint     = 1;
        rtt_d_flag = 1;
    }
    send_hdr.seq++;

    message_send.msg_name    = destination_address;
    message_send.msg_namelen = destination_len;
    message_send.msg_iov     = iov_send;
    message_send.msg_iovlen  = 2;

    iov_send[0].iov_base = &send_hdr;
    iov_send[0].iov_len  = sizeof(struct hdr);

    iov_send[1].iov_base = out_buffer;
    iov_send[1].iov_len  = out_bytes;

    message_recevie.msg_name    = NULL;
    message_recevie.msg_namelen = 0;
    message_recevie.msg_iov     = iov_recevie;
    message_recevie.msg_iovlen  = 2;

    iov_recevie[0].iov_base = &recevie_hdr;
    iov_recevie[0].iov_len  = sizeof(struct hdr);

    iov_recevie[1].iov_base = in_buffer;
    iov_recevie[1].iov_len  = in_bytes;

    // 设置信号处理函数
    signal(SIGALRM, sig_alarm);
    // 重置尝试次数
    rtt_newpack(&rtt_info);

send_message_again:
    send_hdr.ts = rtt_ts(&rtt_info);

    sendmsg(fd, &message_send, 0);
    alarm(rtt_start(&rtt_info));

    if (sigsetjmp(jmp_buff, 1) != 0) {

        if (rtt_timeout(&rtt_info) < 0) {

            printf("dg_send recv : no response from server, giving up");
            rttint = 0;
            errno  = ETIMEDOUT;
            return (-1);
        }
        goto send_message_again;
    }
    do {

        n = recvmsg(fd, &message_recevie, 0);
    } while(n < sizeof(struct hdr) || recevie_hdr.seq != send_hdr.seq);

    alarm(0);
    // 更新参数
    rtt_stop(&rtt_info, rtt_ts(&rtt_info) - recevie_hdr.ts);

    return (n - sizeof(struct hdr));
}

int main(int argc, char * argv[]) {

    const char * ip = "xxx.xxx.xxx.xxx";
    uint16_t   port = 7;

    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    SAI server_address;
    bzero(&server_address, sizeof(server_address));
    
    server_address.sin_family = AF_INET;
    server_address.sin_port   = htons(port);
    inet_pton(AF_INET, ip, &server_address.sin_addr);

    if (connect(sockfd, (SA*)&server_address, sizeof(server_address)) == 0) {

        dg_cli(stdin, sockfd, (SA*)&server_address, sizeof(server_address));
    }
    else {

        printf("connect failure\n");
    }
    close(sockfd);
    return EXIT_SUCCESS;
}

// build : gcc -Wall -Wshadow -g dg_cli.c rtt.c -o dg_cli -I./
